#include "SuperBlockData.h"

#include <math.h>


SuperBlockData::SuperBlockData(DiskAbstraction* da, unsigned int iNodesCount):
_da(da),
_extraFreeInodesBlocks(0),
_extraFreeBlocksBlocks(0),
_diskSize(0),
_blockSize(0),
_inodeArraySize(0)
{
	if (_da==NULL)
	{
		throw Exception("Null Pointer Exception - Disk Abstraction is NULL");
	}
	if (iNodesCount==0)
	{
		throw Exception("Illegal Argument Exception - iNodes count is 0");
	}
	if(_da->isNew()){
		initSuperBlock(iNodesCount);
	}else{
		calculateTotalExtraBlocks(iNodesCount);
		_da->seekAndRead(0,sizeof(unsigned int),SuperBlockData::DISK_SIZE,&_diskSize,sizeof(unsigned int));
		_da->seekAndRead(0,sizeof(unsigned int),SuperBlockData::BLOCK_SIZE,&_blockSize,sizeof(unsigned int));
		_da->seekAndRead(0,sizeof(unsigned int),SuperBlockData::INODE_ARRAY_SIZE,&_inodeArraySize,sizeof(unsigned int));
	}
}

SuperBlockData::~SuperBlockData(void)
{
}

void SuperBlockData::initSuperBlock(unsigned int iNodesCount){
	unsigned int dataSize=_da->getBlockSize()/sizeof(unsigned int);
	unsigned int* data=new unsigned int[dataSize];
	for(unsigned int i=0;i<dataSize;i++){
		data[i]=0;
	}
	data[DISK_SIZE]=_da->getDiskSize();
	data[BLOCK_SIZE]=_da->getBlockSize();
	buildFreeInodesBlock(iNodesCount, data);
	buildFreeBlocksBlock(iNodesCount, data);
	_da->write(0,data);
	_diskSize=data[DISK_SIZE];
	_blockSize=data[BLOCK_SIZE];
	_da->seekAndRead(0,sizeof(unsigned int),SuperBlockData::INODE_ARRAY_SIZE,&_inodeArraySize,sizeof(unsigned int));
	delete data;
}

unsigned int SuperBlockData::get(DataIndex type){
	unsigned int res(0);
	switch (type){
	case DISK_SIZE:
		res=_diskSize;
		break;
	case BLOCK_SIZE:
		res=_blockSize;
		break;
	case INODE_ARRAY_SIZE:
		res=_inodeArraySize;
		break;
	default:
		_da->seekAndRead(0,sizeof(unsigned int),type,&res,sizeof(unsigned int));
	}
	return res;
}
void SuperBlockData::set(DataIndex type, unsigned int value){
	switch (type)
	{
	case SuperBlockData::DISK_SIZE:
	case SuperBlockData::BLOCK_SIZE:
	case SuperBlockData::INODE_ARRAY_SIZE:
		throw Exception("Disk/Block/iNodes_arr Size are read only.");
		break;
	}
	_da->seekAndWrite(0,sizeof(unsigned int),type,&value,sizeof(unsigned int));
}

void SuperBlockData::buildFreeInodesBlock(unsigned int iNodesCount,unsigned int* metaDataContainer){
	metaDataContainer[ROOT_INODE]=0;
	metaDataContainer[INODE_ARRAY_SIZE] = iNodesCount;
	metaDataContainer[FREE_INODES_COUNT] = iNodesCount;
	metaDataContainer[FIRST_FREE_INODES_BLOCK]=2;
	metaDataContainer[FIRST_FREE_INODE_OFFSET]=0;
	unsigned int addressesPerBlock(_da->getBlockSize()/sizeof(unsigned int)-1);
	unsigned int neededBlocksCount((unsigned int)ceil(iNodesCount/(double)addressesPerBlock));
	_extraFreeInodesBlocks=neededBlocksCount-1;
	if(2+iNodesCount+neededBlocksCount>_da->getTotalBlockCount()){
		throw Exception("Not enough space for inodes and its free inodes blocks.");
	}
	unsigned int nextFreeDataBlock(2+iNodesCount);
	metaDataContainer[LAST_FREE_INODS_BLOCK]=metaDataContainer[FIRST_FREE_INODES_BLOCK];
	if (neededBlocksCount>1){
		metaDataContainer[LAST_FREE_INODS_BLOCK]=neededBlocksCount-1+nextFreeDataBlock;
	}
	metaDataContainer[LAST_FREE_INODE_OFFSET]=(iNodesCount-1)%addressesPerBlock;
	unsigned int currentWorkingBlock(metaDataContainer[FIRST_FREE_INODES_BLOCK]);
	for (unsigned int i=0;i<neededBlocksCount;i++) {
		unsigned int startAddress(i*addressesPerBlock+3);
		if (i!=0){
			currentWorkingBlock=nextFreeDataBlock;
		}
		if (i!=neededBlocksCount-1){
			nextFreeDataBlock++;
		} else {
			nextFreeDataBlock=metaDataContainer[FIRST_FREE_INODES_BLOCK];
		}
		writeMetaDataAddresses(currentWorkingBlock,startAddress,iNodesCount+2,nextFreeDataBlock);
	}
}

void SuperBlockData::buildFreeBlocksBlock(unsigned int iNodesCount,unsigned int* metaDataContainer){
	metaDataContainer[FIRST_FREE_BLOCKS_BLOCK] = 1;
	metaDataContainer[FIRST_FREE_BLOCK_OFFSET] = 0;
	unsigned int addressesPerBlock(_da->getBlockSize()/sizeof(unsigned int)-1);
	unsigned int totalFreeBlocks(_da->getTotalBlockCount()-iNodesCount-(unsigned int)ceil(iNodesCount/(double)addressesPerBlock)-1);
	metaDataContainer[FREE_BLOCKS_COUNT] = totalFreeBlocks*addressesPerBlock/(1+addressesPerBlock);
	if(metaDataContainer[FREE_BLOCKS_COUNT]==0){
		throw Exception("Not enough space for free blocks");
	}
	unsigned int freeBlocksBlocksCount(totalFreeBlocks-metaDataContainer[FREE_BLOCKS_COUNT]);
	if ((freeBlocksBlocksCount-1)*addressesPerBlock>=metaDataContainer[FREE_BLOCKS_COUNT]){
		freeBlocksBlocksCount--;
	}
	_extraFreeBlocksBlocks=freeBlocksBlocksCount-1;
	unsigned int firstFreeBlockAddress(3+iNodesCount);
	if (iNodesCount>addressesPerBlock){
		firstFreeBlockAddress = metaDataContainer[LAST_FREE_INODS_BLOCK]+1;
	}
	metaDataContainer[LAST_FREE_BLOCKS_BLOCK] = metaDataContainer[FIRST_FREE_BLOCKS_BLOCK];
	if(metaDataContainer[FREE_BLOCKS_COUNT]>addressesPerBlock){
		metaDataContainer[LAST_FREE_BLOCKS_BLOCK] = firstFreeBlockAddress+freeBlocksBlocksCount-2;
	}
	metaDataContainer[LAST_FREE_BLOCK_OFFSET] = (metaDataContainer[FREE_BLOCKS_COUNT]-1)%addressesPerBlock;
	unsigned int currentWorkingBlock(metaDataContainer[FIRST_FREE_BLOCKS_BLOCK]);
	unsigned int nextFreeDataBlock(firstFreeBlockAddress-1);
	for (unsigned int i=0;i<freeBlocksBlocksCount;i++){
		if (i!=0){
			currentWorkingBlock = firstFreeBlockAddress + i - 1;
		}
		unsigned int startAddress(i*addressesPerBlock+firstFreeBlockAddress+freeBlocksBlocksCount-1);
		if (i!=freeBlocksBlocksCount-1){
			nextFreeDataBlock++;
		} else {
			nextFreeDataBlock=metaDataContainer[FIRST_FREE_BLOCKS_BLOCK];
		}
		writeMetaDataAddresses(currentWorkingBlock,startAddress,_da->getTotalBlockCount()-1,nextFreeDataBlock);
	}
}

void SuperBlockData::writeMetaDataAddresses(unsigned int target, unsigned int startAddress, unsigned int endAddress, unsigned int nextBlock){
	unsigned int addressesPerBlock(_da->getBlockSize()/sizeof(unsigned int));
	unsigned int* buffer=new unsigned int[addressesPerBlock+1];
	if (buffer==NULL){
		//TODO exit
	}
	for (unsigned int i=0;i<addressesPerBlock-1;i++) {
		unsigned int nextAddress(i+startAddress);
		if(nextAddress>endAddress){
			buffer[i]=0;
		}else{
			buffer[i]=nextAddress;
		}
	}
	buffer[addressesPerBlock-1]=nextBlock;
	_da->write(target,buffer);
	delete buffer;
}

void SuperBlockData::calculateTotalExtraBlocks(unsigned int iNodesCount){
	unsigned int addressesPerBlock(_da->getBlockSize()/sizeof(unsigned int)-1);
	_extraFreeInodesBlocks=(unsigned int)ceil(iNodesCount/(double)addressesPerBlock)-1;

	unsigned int totalFreeBlocks(_da->getTotalBlockCount()-iNodesCount-(unsigned int)ceil(iNodesCount/(double)addressesPerBlock)-1);
	unsigned int freeBlocksCount(totalFreeBlocks*addressesPerBlock/(1+addressesPerBlock));
	unsigned int freeBlocksBlocksCount(totalFreeBlocks-freeBlocksCount);
	if ((freeBlocksBlocksCount-1)*addressesPerBlock>=freeBlocksCount){
		freeBlocksBlocksCount--;
	}
	_extraFreeBlocksBlocks=freeBlocksBlocksCount-1;
}


unsigned int SuperBlockData::getExtraFreeInodesBlocksCount(){
	return _extraFreeInodesBlocks;
}

unsigned int SuperBlockData::getExtraFreeBlocksBlocksCount(){
	return _extraFreeBlocksBlocks;
}
